package mylittleinvestigations;

import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;

public class PulseTrack
{
    private double OVAL_ROTATION = 0d;
    private double BALL_SPEED = 1d; // radians/s
    private double OVAL_SPEED = 0d;

    private double OVAL_CENTER_X = 0; // px
    private double OVAL_CENTER_Y = 0; // px

    private static final double OVAL_LINE_THICKNESS = 8; // px

    // Angle of rotation along the oval.
    private double theta = 0; // radians

    // Wide and tall radii of the oval.
    private double wideRadius = 0; // px
    private double tallRadius = 2; // px

    private double ballCenterX = 0; // px
    private double ballCenterY = 0; // px

    private Image pulsePath=null;
    private Image pulseBall=null;

    public void setPathImage(Image image)
    {
        pulsePath=image.copy();
        wideRadius = pulsePath.getWidth() / 2 - OVAL_LINE_THICKNESS / 2;
        tallRadius = pulsePath.getHeight() / 2 - OVAL_LINE_THICKNESS / 2;
    }
    public void setBallImage(Image image)
    {
        pulseBall=image.copy();
    }
    public void update(int delta)
    {
        if(OVAL_SPEED!=0d)
        {
            OVAL_ROTATION+=(((double)delta/1000.0d)*OVAL_SPEED);
            pulsePath.setRotation((float)(OVAL_ROTATION*(180d/Math.PI)));
        }
        theta += BALL_SPEED * (double) delta / 1000.0d;

        // First we get the x- and y-coordinates of the ball's center
        // with the oval's center placed at the origin.
        ballCenterX = wideRadius * Math.cos(theta);
        ballCenterY = tallRadius * Math.sin(theta);

        // Now we rotate this point around the origin the same amount
        // that we rotate the oval around the origin.
        //
        // This is done using the following matrix multiplication:
        //
        // | cos theta  -sin theta | | x |
        // | sin theta   cos theta | | y |
        //
        // where the left matrix is the standard rotational matrix
        // about the origin.
        //
        // Of course, we don't really feel like implementing
        // or finding an actual matrix object just to do this
        // one bit of matrix multiplication, so the below
        // is the expanded scalar form of the multiplication.
        //
        // We define new variables here because if we reused
        // ballCenterX and ballCenterY, then the second
        // statement would incorrectly be using the already-rotated
        // value of ballCenterX.
        double rotatedBallCenterX = ballCenterX * Math.cos(OVAL_ROTATION) - ballCenterY * Math.sin(OVAL_ROTATION);
        double rotatedBallCenterY = ballCenterX * Math.sin(OVAL_ROTATION) + ballCenterY * Math.cos(OVAL_ROTATION);
        
        // Now we need to update the rotation of the ball.
        // Since it's moving around in an ovular path,
        // its direction should always be along the line
        // tangent to its current point on the oval.
        //
        // The general equation of an ellipse is
        //
        //   (x / a)^2 + (y / b)^2 = 1
        //
        // Differentiating both sides to find the slope,
        // we get
        //
        //   x / a^2 + (dy/dx) (y / b^2) = 0
        //
        // Solving for dy/dx,
        //
        //   dy/dx = (b^2 / a^2) (x / y)
        //
        // So the slope at the point (x, y) is equal to
        // (b^2 / a^2) (x / y).  However, we need the
        // angle of rotation to be applied to the ball,
        // not the slope of the tangent line.
        // The slope of a line is equal to the amount
        // it rises vertically for every horizontal unit,
        // so we can get this angle of rotation relative
        // to the horizontal axis by forming a right-angled
        // triangle using a horizontal line of length 1
        // and a vertical line of length dy/dx.
        // Then we get that
        //
        //   theta = arctan(dy/dx / 1)
        //   theta = arctan((b^2 / a^2) (x / y))
        //
        // This is the angle of rotation that we need to apply
        // to the ball.
        //
        // Note that one special case is when y is 0.
        // That will give us a division by zero, which is bad.
        // When y is zero, the rotation should be 90 degrees,
        // so we can just set it to that.
        float rotation = 0;
        
        if (ballCenterY == 0)
        {
            rotation = 90;
        }
        else
        {
            double slope = ((tallRadius * tallRadius) / (wideRadius * wideRadius)) * (ballCenterX / ballCenterY);
            rotation = (float)(Math.atan(slope) * 180 / Math.PI);
        }
        
        // Due to the way the arctan function works,
        // this will always only give us a rotation between 180 and 0.
        // The ball's going in the opposite direction during the top
        // portion of the oval, so we'll add on 180 degrees
        // in this case to make the ball's motion fluid across
        // the whole oval.
        if (ballCenterY < 0)
        {
            rotation += 180;
        }
        
        // If the ball speed is negative, then the ball is going in the
        // opposite direction, so add another 180 degrees of rotation
        // if that's the case.
        if (BALL_SPEED < 0)
        {
            rotation += 180;
        }
        
        // The ball is pointing upwards, which means it already begins
        // rotated.  We'll subtract 90 degrees of rotation in order
        // to compensate for that.
        rotation -= 90;
        
        // The above produces a rotation going outwards up and to the right
        // from the horizontal axix.  We want the opposite for the ball -
        // it should be pointing down and to the left to hug the curvature
        // of the oval.  We'll negate the rotation to account for this.
        //
        // Finally, we also need to add in the oval's rotation to the
        // ball's rotation, in order to make sure it's situated properly
        // according to the oval's state.
        pulseBall.setRotation(-rotation + (float)(OVAL_ROTATION * 180 / Math.PI));

        // Finally, we translate the origin to the point
        // at which we've placed the center of the oval.
        ballCenterX = rotatedBallCenterX + OVAL_CENTER_X;
        ballCenterY = rotatedBallCenterY + OVAL_CENTER_Y;
    }
    public void drawTrack(Graphics g)
    {
        //if(pulsePath!=null) g.drawImage(pulsePath, (float)(OVAL_CENTER_X - pulsePath.getWidth() / 2), (float)(OVAL_CENTER_Y - pulsePath.getHeight() / 2));
    }
    public void drawBall(Graphics g)
    {
        if(pulseBall!=null) g.drawImage(pulseBall, (float)ballCenterX - pulseBall.getWidth() / 2, (float)ballCenterY - pulseBall.getHeight() / 2);
    }
    public void setRotation(double rotation)
    {
        OVAL_ROTATION=rotation*(Math.PI/180);
        pulsePath.setRotation((float)rotation);
    }
    public void setPosition(double x,double y)
    {
        OVAL_CENTER_X=x;
        OVAL_CENTER_Y=y;
    }
    public void setInitialTheta(double theta)
    {
        this.theta = theta;
    }
    public void setTrackSpeed(double speed)
    {
        OVAL_SPEED=speed;
    }
    public void setBallSpeed(double speed)
    {
        BALL_SPEED=speed;
    }
}
